use std::sync::Arc;

use tokio::sync::Mutex;

use bc_base::{
    BlockHeight, BlockId, TransactionId,
    block::{Block, BlockInDb, BlockIndexNode},
    transaction::Transaction,
};

use bc_lib::top_storage::{
    BlockAndIndexStorageWithCache, MiscItemStorageWithCache, TransactionAndIndexStorageWithCache,
};

use crate::block::block_index::BlockIndexError;

#[derive(Debug)]
pub struct BlockIndexDataManager {
    block_and_index_storage_with_cache: Arc<Mutex<BlockAndIndexStorageWithCache>>,
    tx_and_index_storage_with_cache: Arc<Mutex<TransactionAndIndexStorageWithCache>>,
    misc_item_storage_with_cache: Arc<Mutex<MiscItemStorageWithCache>>,
}

impl BlockIndexDataManager {
    pub fn new(
        block_and_index_storage_with_cache: Arc<Mutex<BlockAndIndexStorageWithCache>>,
        tx_and_index_storage_with_cache: Arc<Mutex<TransactionAndIndexStorageWithCache>>,
        misc_item_storage_with_cache: Arc<Mutex<MiscItemStorageWithCache>>,
    ) -> Self {
        Self {
            block_and_index_storage_with_cache,
            tx_and_index_storage_with_cache,
            misc_item_storage_with_cache,
        }
    }
}

// simple wrapper for read
impl BlockIndexDataManager {
    pub async fn read_latest_block_id_in_best_chain(&self) -> BlockId {
        self.misc_item_storage_with_cache
            .lock()
            .await
            .read_latest_block_id_in_best_chain()
    }

    pub async fn read_best_height(&self) -> BlockHeight {
        self.misc_item_storage_with_cache
            .lock()
            .await
            .read_best_height()
    }

    pub async fn block_exists_in_db(&self, block_id: &BlockId) -> bool {
        self.block_and_index_storage_with_cache
            .lock()
            .await
            .block_index_exists(block_id)
            .await
    }

    pub async fn tx_exists_in_db(&self, tx_id: &TransactionId) -> bool {
        self.tx_and_index_storage_with_cache
            .lock()
            .await
            .tx_exists(tx_id)
            .await
    }

    pub async fn block_index_exists_in_cache(&self, block_id: &BlockId) -> bool {
        self.block_and_index_storage_with_cache
            .lock()
            .await
            .block_index_exists(block_id)
            .await
    }

    pub async fn read_block_index_node_in_cache(
        &self,
        block_id: &BlockId,
    ) -> Option<BlockIndexNode> {
        self.block_and_index_storage_with_cache
            .lock()
            .await
            .read_block_index_node(block_id)
    }

    pub async fn get_prev_n_timestame(&self, block_id: &BlockId, num: usize) -> Vec<u32> {
        self.block_and_index_storage_with_cache
            .lock()
            .await
            .get_prev_n_timestame(block_id, num)
    }

    pub async fn read_block_from_db(
        &self,
        block_id: &BlockId,
    ) -> Result<BlockInDb, BlockIndexError> {
        let t = self
            .block_and_index_storage_with_cache
            .lock()
            .await
            .read_block_in_db(block_id)
            .await?;
        Ok(t)
    }

    pub async fn read_block_and_txs(&self, block_id: &BlockId) -> Result<Block, BlockIndexError> {
        let block_in_db = self
            .block_and_index_storage_with_cache
            .lock()
            .await
            .read_block_in_db(block_id)
            .await?;

        self.do_read_block_and_txs(&block_in_db).await
    }

    async fn do_read_block_and_txs(
        &self,
        block_in_db: &BlockInDb,
    ) -> Result<Block, BlockIndexError> {
        let mut t = Block::new(block_in_db.get_block_header().clone());
        for tx_id in block_in_db.get_transaction_ids() {
            let tx = self
                .tx_and_index_storage_with_cache
                .lock()
                .await
                .read_tx(tx_id)
                .await?;
            t.add_transaction(tx);
        }

        Ok(t)
    }

    pub async fn dump_all_block(&self) {
        let block_and_index = self.block_and_index_storage_with_cache.lock().await;
        // let mut index = 0;
        for (index, (block_id, _block_index)) in block_and_index.iter_block_index().enumerate() {
            let block_in_db = block_and_index.read_block_in_db(block_id).await.unwrap();
            let block = self.do_read_block_and_txs(&block_in_db).await.unwrap();
            println!("\n[No.{}][ID-{}] {:#?}", index, block_id, block);
            println!("txs-id:{:?}", block_in_db.get_transaction_ids());
            // index += 1;
        }
    }
}

// simple wrapper for write
impl BlockIndexDataManager {
    pub async fn write_block_and_index_sync(
        &self,
        block_id: &BlockId,
        block: &Block,
    ) -> Result<(), BlockIndexError> {
        self.block_and_index_storage_with_cache
            .lock()
            .await
            .write_block_and_index_sync(block_id, block)
            .await?;
        Ok(())
    }

    pub async fn delete_block_and_txs(&self, block_id: &BlockId) -> Result<(), BlockIndexError> {
        let block_in_db = self
            .block_and_index_storage_with_cache
            .lock()
            .await
            .read_block_in_db(block_id)
            .await?;

        for tx_id in block_in_db.get_transaction_ids() {
            self.tx_and_index_storage_with_cache
                .lock()
                .await
                .delete_tx_and_index_sync(tx_id)
                .await?;
        }

        self.block_and_index_storage_with_cache
            .lock()
            .await
            .delete_block_and_index_sync(block_id)
            .await?;

        Ok(())
    }

    pub async fn delete_block_and_index_sync(
        &self,
        block_id: &BlockId,
    ) -> Result<(), BlockIndexError> {
        self.block_and_index_storage_with_cache
            .lock()
            .await
            .delete_block_and_index_sync(block_id)
            .await?;

        Ok(())
    }

    pub async fn set_next_block_id(
        &mut self,
        block_id: &BlockId,
        next_blk_id: &BlockId,
    ) -> Result<(), BlockIndexError> {
        self.block_and_index_storage_with_cache
            .lock()
            .await
            .set_next_block_id_sync(block_id, next_blk_id)
            .await?;
        Ok(())
    }

    pub async fn reset_next_block_id(&mut self, block_id: &BlockId) -> Result<(), BlockIndexError> {
        self.block_and_index_storage_with_cache
            .lock()
            .await
            .reset_next_block_id_sync(block_id)
            .await?;
        Ok(())
    }

    pub async fn update_block_height_sync(
        &mut self,
        block_id: &BlockId,
        block_height: BlockHeight,
    ) -> Result<(), BlockIndexError> {
        self.block_and_index_storage_with_cache
            .lock()
            .await
            .update_block_height_sync(block_id, block_height)
            .await?;
        Ok(())
    }

    pub async fn write_tx_and_index_sync(
        &mut self,
        tx_id: &TransactionId,
        tx: &Transaction,
        belong_to_block: Option<BlockId>,
    ) -> Result<(), BlockIndexError> {
        self.tx_and_index_storage_with_cache
            .lock()
            .await
            .write_tx_and_index_sync(tx_id, tx, belong_to_block)
            .await?;
        Ok(())
    }

    pub async fn delete_tx_and_index_sync(
        &mut self,
        tx_id: &TransactionId,
    ) -> Result<(), BlockIndexError> {
        self.tx_and_index_storage_with_cache
            .lock()
            .await
            .delete_tx_and_index_sync(tx_id)
            .await?;
        Ok(())
    }

    pub async fn write_latest_blk_id_in_best_chain(
        &mut self,
        block_id: &BlockId,
    ) -> Result<(), BlockIndexError> {
        self.misc_item_storage_with_cache
            .lock()
            .await
            .write_latest_blk_id_in_best_chain(block_id)
            .await?;
        Ok(())
    }

    pub async fn write_best_height(
        &mut self,
        best_height: BlockHeight,
    ) -> Result<(), BlockIndexError> {
        self.misc_item_storage_with_cache
            .lock()
            .await
            .write_best_height(best_height)
            .await?;
        Ok(())
    }
    pub async fn write_time_best_received(&mut self) -> Result<(), BlockIndexError> {
        self.misc_item_storage_with_cache
            .lock()
            .await
            .write_time_best_received()
            .await?;
        Ok(())
    }
}
